﻿using System;
using System.Drawing;
using System.Windows.Forms;
using FireStarter.Base;
using Firestarter.DB.Schema.BO;
using Firestarter.DB.Schema.Test.BO.TestUtils;
using Firestarter.DB.Schema.UI;
using Habanero.Base.Exceptions;
using Habanero.Faces.Win;
using Habanero.Test;
using Habanero.Faces.Base;
using Habanero.Testability;
using NUnit.Framework;
using DockStyle = System.Windows.Forms.DockStyle;
using System.Collections.Generic;
using FormBorderStyle = Habanero.Faces.Base.FormBorderStyle;
using FormStartPosition = Habanero.Faces.Base.FormStartPosition;

namespace Firestarter.DB.Schema.Test.UI
{
    [TestFixture]
    public class TestDatabaseInfoContextMenuController : TestBase
    {

        private System.Windows.Forms.Form _testForm;


        [SetUp]
        public override void SetupTest()
        {
            CreateForm();
        }

        [TearDown]
        public void TearDownTest()
        {
            CloseForm();
        }

        // ReSharper disable InconsistentNaming
        [Test]
        public void Test_Construct_MustHaveTreeViewAsParameter()
        {
            //---------------Set up test pack-------------------
            var treeView = new DatabaseInfoTreeViewWin();
            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------
            var controller = new DatabaseInfoContextMenuController(treeView);
            //---------------Test Result -----------------------
            Assert.IsNotNull(controller);
            Assert.IsNotNull(controller.TreeView);
            Assert.AreSame(treeView, controller.TreeView);
        }


        [Test]
        public void Test_GetMenuItemName_WhenGivenDBTableBO_ShouldReturn_Merge_rename_table()
        {
            //---------------Set up test pack-------------------
            const string expected = "Merge renamed table...";
            //---------------Assert Precondition----------------
            
            //---------------Execute Test ----------------------
            var dbTable = new DBTable(GetRandomString());
            var actual = DatabaseInfoContextMenuController.GetMenuItemName(dbTable);
            //---------------Test Result -----------------------
            Assert.AreEqual(expected, actual);
        }

        [Test]
        public void Test_GetMenuItemName_WhenGivenDBColumnBO_ShouldReturn_Merge_rename_column()
        {
            //---------------Set up test pack-------------------
            const string expected = "Merge renamed column...";
            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------
            var column = new DBColumn(GetRandomString());
            var actual = DatabaseInfoContextMenuController.GetMenuItemName(column);
            //---------------Test Result -----------------------
            Assert.AreEqual(expected, actual);
        }

        [Test]
        public void Test_GetMenuItemName_WhenNotDBTableBOAndNotDBColumnBO_ShouldReturnThrowException()
        {
            //---------------Set up test pack-------------------
            var fk = new DBForeignKey();
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            try
            {
                DatabaseInfoContextMenuController.GetMenuItemName(fk);
                
                //---------------Test Result -----------------------
                Assert.Fail("Expected to throw an EXCEPTION");
            } 
            catch (Exception ex)
            {
                Assert.IsInstanceOf<HabaneroDeveloperException>(ex);
                StringAssert.Contains("Only DBTable and DBColumn can be used.", ex.Message);
            }
            //---------------Test Result -----------------------
        }

        [Test]
        public void Test_SetupMenuItem_WhenCalled_ShouldCreateTwoItemContextMenu()
        {
            //---------------Set up test pack-------------------
            var controller = CreateDatabaseInfoContextMenuController();
            var selectedBo = new DBTable("selectedTable");
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            controller.SetupMenuItem(selectedBo);
            //---------------Test Result -----------------------
            Assert.AreEqual(2, controller.TreeViewContextMenu.MenuItems.Count);
        }

        [Test]
        public void Test_SetupMenuItem_WhenCalled_ShouldHaveTableMenuItemAndColumnMenuItem()
        {
            //---------------Set up test pack-------------------
            var controller = CreateDatabaseInfoContextMenuController();
            var selectedBo = new DBTable(GetRandomString());
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            controller.SetupMenuItem(selectedBo);
            //---------------Test Result -----------------------
            StringAssert.Contains("table", controller.TreeViewContextMenu.MenuItems[0].Text);
            StringAssert.Contains("column", controller.TreeViewContextMenu.MenuItems[1].Text);
        }

        [Test]
        public void Test_GetMenuItem_WhenSelectedBoIsDBTable_ShouldReturnMenuItemNameForTable()
        {
            //---------------Set up test pack-------------------
            var controller = CreateDatabaseInfoContextMenuController();
            var selectedBo = new DBTable("selectedTable");
            controller.SetupMenuItem(selectedBo);
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            var menuItem = controller.GetMenuItem(selectedBo);
            //---------------Test Result -----------------------
            Assert.IsNotNull(menuItem);
            StringAssert.Contains(" table", menuItem.Text);
        }

        [Test]
        public void Test_GetMenuItem_WhenSelectedBoIsDBColumn_ShouldReturnMenuItemNameForColumn()
        {
            //---------------Set up test pack-------------------
            var controller = CreateDatabaseInfoContextMenuController();
            var dbColumn = new DBColumn("a_column");
            controller.SetupMenuItem(dbColumn);
            //---------------Assert Precondition----------------
            //---------------Execute Test ----------------------
            var menuItem = controller.GetMenuItem(dbColumn);
            //---------------Test Result -----------------------
            Assert.IsNotNull(menuItem);
            StringAssert.Contains(" column", menuItem.Text);
        }

        [Test]
        public void Test_MenuItemShouldBeEnabled_WhenBoChangeTypeIsAdd_ShouldReturn_True()
        {
            //---------------Set up test pack-------------------
            var mergeableBo = new DBTable("table") {PendingChangeType = ChangeType.Add};
            //---------------Assert Precondition----------------
            Assert.AreEqual(ChangeType.Add, mergeableBo.PendingChangeType);
            //---------------Execute Test ----------------------
            var enabled  = DatabaseInfoContextMenuController.MenuItemShouldBeEnabled(mergeableBo);
            //---------------Test Result -----------------------
            Assert.IsTrue(enabled);
        }

        [Test]
        public void Test_MenuItemShouldBeEnabled_WhenBoChangeTypeIsDelete_ShouldReturn_True()
        {
            //---------------Set up test pack-------------------
            DatabaseInfoContextMenuController controller = CreateDatabaseInfoContextMenuController();
            var mergeableBo = new DBTable("table") {PendingChangeType = ChangeType.Delete };
            //---------------Assert Precondition----------------
            Assert.AreEqual(ChangeType.Delete, mergeableBo.PendingChangeType);
            //---------------Execute Test ----------------------
            var enabled = DatabaseInfoContextMenuController.MenuItemShouldBeEnabled(mergeableBo);
            //---------------Test Result -----------------------
            Assert.IsTrue(enabled);
        }

        [Test]
        public void Test_MenuItemShouldBeEnabled_WhenBoChangeTypeIsNotAddAndIsNotDelete_ShouldReturn_False()
        {
            //---------------Set up test pack-------------------
            DatabaseInfoContextMenuController controller = CreateDatabaseInfoContextMenuController();
            var mergeableBo = new DBTable("table") { PendingChangeType = ChangeType.None };
            //---------------Assert Precondition----------------
            Assert.AreEqual(ChangeType.None, mergeableBo.PendingChangeType);
            //---------------Execute Test ----------------------
            var enabled = DatabaseInfoContextMenuController.MenuItemShouldBeEnabled(mergeableBo);
            //---------------Test Result -----------------------
            Assert.IsFalse(enabled);
        }

        [Test]
        public void Test_DatabaseInfoContextMenuController_TreeView_HasMouseClickEventHandler()
        {
            //---------------Set up test pack-------------------
            DatabaseInfoTreeViewWin treeView = new DatabaseInfoTreeViewWin();
            DatabaseInfoContextMenuController controller = new DatabaseInfoContextMenuController(treeView);
            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------
            int actualCountEventSubscriber = TestUtil.CountEventSubscribers(controller.TreeView, "MouseClick");
            //---------------Test Result -----------------------
            Assert.AreEqual(1, actualCountEventSubscriber);
            Assert.IsTrue
                (TestUtil.EventHasSubscriber(controller.TreeView, "MouseClick", "MouseClickEventHandler"));
        }


        [Test]
        public void Test_MouseClickEventHandler_WhenClickedWithLeftMouseButton_ShouldNotGetTreeNode()
        {
            //.
            //---------------Set up test pack-------------------
            DatabaseInfoTreeViewWin treeView = new DatabaseInfoTreeViewWin();
            var controller = new DatabaseInfoContextMenuControllerSpy(treeView);
            treeView.ExpandAll();
            //---------------Assert Precondition----------------
            Assert.IsFalse(controller.GetTreeNodeWasCalled);
            //---------------Execute Test ----------------------
            treeView.PerformMouseClick(GetDummyLeftMouseClickEventArgs());
            //---------------Test Result -----------------------
            Assert.IsFalse(controller.GetTreeNodeWasCalled, "The left button should not cause the context menu to popup");

        }

        [Test]
        public void Test_PerformMouseClick_WhenClickedWithRightMouseButtonAndTreeViewNodeIsDBTable_ShouldSetMenuItem()
        {
            //---------------Set up test pack-------------------
            DatabaseInfoTreeViewWin treeView = new DatabaseInfoTreeViewWin();
            TreeViewController treeViewController = new TreeViewController(treeView);

            DBDatabase dbDatabase = new DBDatabase();
            DBTable dbTable = new DBTable("table") { PendingChangeType = ChangeType.Add };
            dbDatabase.Tables.Add(dbTable);
            treeViewController.LoadTreeView(dbDatabase);
            AddControlToForm(treeView);
            var controller = new DatabaseInfoContextMenuControllerSpy(treeView);
            AddControlToForm(treeView);
            ShowForm();
            treeView.ExpandAll();
            ITreeNode dbTableTreeNode = treeView.Nodes[0].Nodes[0].Nodes[0];
            controller.SetTreeNode(dbTableTreeNode);
            //---------------Assert Precondition----------------
            Assert.IsInstanceOf<DBTable>(dbTableTreeNode.Tag);
            Assert.IsFalse(controller.GetTreeNodeWasCalled);
            Assert.IsFalse(controller.ShowContextMenuWasCalled);
            //---------------Execute Test ----------------------
            treeView.PerformMouseClick(GetDummyRightMouseClickEventArgs());

            //---------------Test Result -----------------------
            Assert.AreSame(dbTableTreeNode, controller.TreeView.SelectedNode);
            Assert.IsTrue(controller.GetTreeNodeWasCalled);
            Assert.IsTrue(controller.ShowContextMenuWasCalled);
        }

        [Test]
        public void Test_GetPotentialRenamedBOsFor_WhenSelectedBoIsDBTableAndChangeTypeIsAdd_ShouldReturnOneDBTableWithChangeType_Delete()
        {
            //---------------Set up test pack-------------------
            DBTable dbTableAdded;
            DBTable dbTableDeleted;
            DBTable unchangedTable;
            CreateDBWithAddedDeletedAndUnChangedTables(out dbTableAdded, out dbTableDeleted, out unchangedTable);

            //---------------Assert Precondition----------------
            Assert.AreEqual(ChangeType.Add, dbTableAdded.PendingChangeType);
            Assert.AreEqual(ChangeType.Delete, dbTableDeleted.PendingChangeType);
            Assert.AreEqual(ChangeType.None, unchangedTable.PendingChangeType);
            //---------------Execute Test ----------------------
            var boCol = DatabaseInfoContextMenuController.GetPotentialRenamedBOsFor(dbTableAdded);
            //---------------Test Result -----------------------
            Assert.AreEqual(1, boCol.Count);
            Assert.AreSame(dbTableDeleted, boCol[0]);
            Assert.AreEqual(ChangeType.Delete, ((IMergableBo)boCol[0]).PendingChangeType);
        }

        private static void CreateDBWithAddedDeletedAndUnChangedTables(out DBTable dbTableAdded, out DBTable dbTableDeleted, out DBTable unchangedTable)
        {
            var dbDatabase = TestUtilsDBDatabase.GetTestFactory()
                .WithValue(database1 => database1.StoredByReverseEngineering, null)
                .WithMany(database => database.Tables)
                .CreateValidBusinessObject();
            dbTableDeleted = dbDatabase.Tables[0];
            dbTableDeleted.PendingChangeType = ChangeType.Delete;

            dbTableAdded = dbDatabase.Tables[1];
            dbTableAdded.PendingChangeType = ChangeType.Add;
            unchangedTable = dbDatabase.Tables[2];
        }

        [Test]
        public void Test_GetPotentialRenamedBOsFor_WhenSelectedBoIsDBTableAndChangeTypeIsDelete_ShouldReturnOneDBTableWithChangeType_Add()
        {
            //---------------Set up test pack-------------------
            DBTable dbTableAdded;
            DBTable dbTableDeleted;
            DBTable unchangedTable;
            CreateDBWithAddedDeletedAndUnChangedTables(out dbTableAdded, out dbTableDeleted, out unchangedTable);
            //---------------Assert Precondition----------------
            Assert.AreEqual(ChangeType.Add, dbTableAdded.PendingChangeType);
            Assert.AreEqual(ChangeType.Delete, dbTableDeleted.PendingChangeType);
            Assert.AreEqual(ChangeType.None, unchangedTable.PendingChangeType);
            //---------------Execute Test ----------------------
            var boCol = DatabaseInfoContextMenuController.GetPotentialRenamedBOsFor(dbTableDeleted);
            //---------------Test Result -----------------------
            Assert.AreEqual(1, boCol.Count);
            Assert.AreSame(dbTableAdded, boCol[0]);
            Assert.AreEqual(ChangeType.Add, ((IMergableBo)boCol[0]).PendingChangeType);
        }

        [Test]
        public void Test_GetPotentialRenamedBOsFor_WhenSelectedBoIsDBTableAndChangeTypeIsNotDeleteAndNotAdd_ShouldRaiseException()
        {
            //---------------Set up test pack-------------------
            var dbDatabase = TestUtilsDBDatabase.GetTestFactory()
                .WithValue(database1 => database1.StoredByReverseEngineering, null)
                .WithTwo(database => database.Tables)
                .CreateValidBusinessObject();
            DBTable selectedBo = dbDatabase.Tables[1];
            selectedBo.PendingChangeType = ChangeType.None;
            //---------------Assert Precondition----------------
            Assert.AreEqual(ChangeType.None, selectedBo.PendingChangeType);

            //---------------Execute Test ----------------------
            try
            {
                DatabaseInfoContextMenuController.GetPotentialRenamedBOsFor(selectedBo);
                Assert.Fail("Expected to throw an EXCEPTION");
            }
            catch (Exception ex)
            {
                //---------------Test Result -----------------------
                Assert.IsInstanceOf<HabaneroDeveloperException>(ex);
                StringAssert.Contains("Only ChangeTypes of Add and Delete are valid in renaming scenarios.",ex.Message);
            }
        }
        
        [Test]
        public void Test_GetPotentialRenamedBOsFor_WhenSelectedBoIsDBColumnAndChangeTypeIsAdd_ShouldReturnOneDBColuimnWithChangeType_Delete()
        {
            //---------------Set up test pack-------------------
            DBColumn addedColumn;
            DBColumn deletedColumn;
            DBColumn unchangedColumn;
            CreateTableWithAddedDeletedAndUnChangedColumns(out addedColumn, out deletedColumn, out unchangedColumn);

            //---------------Assert Precondition----------------
            Assert.AreEqual(ChangeType.Delete, deletedColumn.PendingChangeType);
            Assert.AreEqual(ChangeType.Add, addedColumn.PendingChangeType);
            Assert.AreEqual(ChangeType.None, unchangedColumn.PendingChangeType);

            //---------------Execute Test ----------------------
            var boCol = DatabaseInfoContextMenuController.GetPotentialRenamedBOsFor(addedColumn);
            //---------------Test Result -----------------------
            Assert.AreEqual(1, boCol.Count);
            Assert.AreSame(deletedColumn, boCol[0]);
            Assert.AreEqual(ChangeType.Delete, ((IMergableBo)boCol[0]).PendingChangeType);
        }

        private static void CreateTableWithAddedDeletedAndUnChangedColumns(out DBColumn addedColumn, out DBColumn deletedColumn, out DBColumn unchangedColumn)
        {
            var dbTable = TestUtilsDBTable.GetTestFactory()
                .WithMany(database => database.Columns)
                .CreateValidBusinessObject();

            deletedColumn = dbTable.Columns[0];
            deletedColumn.PendingChangeType = ChangeType.Delete;

            addedColumn = dbTable.Columns[1];
            addedColumn.PendingChangeType = ChangeType.Add;
            unchangedColumn = dbTable.Columns[2];
        }

        [Test]
        public void Test_GetPotentialRenamedBOsFor_WhenSelectedBoIsDBColumnAndChangeTypeIsDelete_ShouldReturnOneDBColuimnWithChangeType_Add()
        {
            //---------------Set up test pack-------------------
            DBColumn addedColumn;
            DBColumn deletedColumn;
            DBColumn unchangedColumn;
            CreateTableWithAddedDeletedAndUnChangedColumns(out addedColumn, out deletedColumn, out unchangedColumn);

            //---------------Assert Precondition----------------
            Assert.AreEqual(ChangeType.Delete, deletedColumn.PendingChangeType);
            Assert.AreEqual(ChangeType.Add, addedColumn.PendingChangeType);
            Assert.AreEqual(ChangeType.None, unchangedColumn.PendingChangeType);

            //---------------Execute Test ----------------------
            var boCol = DatabaseInfoContextMenuController.GetPotentialRenamedBOsFor(deletedColumn);
            //---------------Test Result -----------------------
            Assert.AreEqual(1, boCol.Count);
            Assert.AreSame(addedColumn, boCol[0]);
            Assert.AreEqual(ChangeType.Add, ((IMergableBo)boCol[0]).PendingChangeType);
        }

        [Test]
        public void Test_GetPotentialRenamedBOsFor_WhenSelectedBoIsNotDBTableOrDBColumn_ShouldRaiseException()
        {
            //---------------Set up test pack-------------------
            IMergableBo selectedBo = new DBForeignKey();

            //---------------Assert Precondition----------------
            Assert.IsInstanceOf<DBForeignKey>(selectedBo);

            //---------------Execute Test ----------------------
            try
            {
                var boCol = DatabaseInfoContextMenuController.GetPotentialRenamedBOsFor(selectedBo);
            }
            catch (Exception ex)
            {
                //---------------Test Result -----------------------
                Assert.IsInstanceOf<HabaneroDeveloperException>(ex);
                StringAssert.Contains("Only DBTable and DBColumn mergableBo's should be evaluated.", ex.Message);
            }
        }

        private static MouseEventArgs GetDummyLeftMouseClickEventArgs()
        {
            return new MouseEventArgs(MouseButtons.Left, 1, 1, 1, 1);
        }

        private static MouseEventArgs GetDummyRightMouseClickEventArgs()
        {
            return new MouseEventArgs(MouseButtons.Right, 1, 1, 1, 1);
        }


        [Test, Ignore("Visual testing only")]   
        public void Test_ViewMarkAsRenamedForm()
        {
            IControlFactory factory = GlobalUIRegistry.ControlFactory;
            IFormHabanero form = factory.CreateForm();
            form.Size = new System.Drawing.Size(280, 130);
            
            BorderLayoutManager borderLayoutManager = new Habanero.Faces.Win.BorderLayoutManagerWin(form, factory);
            
            MergeBOsControl dialogControl = new MergeBOsControl();
            List<IMergableBo> testList = new List<IMergableBo>();
            testList.Add(new DBTable("table1"));
            testList.Add(new DBTable("table2"));
            //dialogControl.RenameTableList = testList;
            dialogControl.Dock = DockStyle.Fill;
//            dialogControl.BackColor = System.Drawing.Color.DeepPink;
            borderLayoutManager.AddControl(dialogControl);

            form.FormBorderStyle = FormBorderStyle.FixedDialog;

            form.StartPosition = FormStartPosition.CenterParent;
            form.ShowDialog();
        }


        private static DatabaseInfoContextMenuController CreateDatabaseInfoContextMenuController()
        {
            DatabaseInfoTreeViewWin treeView = new DatabaseInfoTreeViewWin(GlobalUIRegistry.ControlFactory);
            return new DatabaseInfoContextMenuController(treeView);
        }


        private static DatabaseInfoContextMenuController CreateDatabaseInfoContextMenuControllerWithLoadedTreeView(DBDatabase dbDatabase)
        {
            DatabaseInfoTreeViewWin treeView = new DatabaseInfoTreeViewWin();
            TreeViewController treeViewController = new TreeViewController(treeView);
            treeViewController.LoadTreeView(dbDatabase);
            var controller = new DatabaseInfoContextMenuControllerSpy(treeView);
            return controller;
        }


        private static string GetRandomString()
        {
            return RandomValueGen.GetRandomString();
        }

        protected void AddControlToForm(IControlHabanero control)
        {
            AddControlToForm(control, 400);
        }

        protected void AddControlToForm(IControlHabanero control, int formHeight)
        {
            _testForm.Controls.Add((System.Windows.Forms.Control)control);
            _testForm.Height = formHeight;
        }

        private void ShowForm()
        {
            _testForm.Show();
        }

        private void CreateForm()
        {
            if (_testForm == null) _testForm = new Form();            
        }

        private Form GetForm()
        {
            return _testForm;
        }

        private void CloseForm()
        {
            if (_testForm == null) return;
            _testForm.Close();
            _testForm = null;
        }

        // ReSharper restore InconsistentNaming
    }

    
    internal class DatabaseInfoContextMenuControllerSpy : DatabaseInfoContextMenuController
    {
        private ITreeNode _treeNode;
        public bool GetTreeNodeWasCalled { private set; get; }
        public bool ShowContextMenuWasCalled { private set; get; }


        public DatabaseInfoContextMenuControllerSpy(DatabaseInfoTreeViewWin treeView) : base(treeView)
        {

        }

        internal void SetTreeNode(ITreeNode treeNode)
        {
            _treeNode = treeNode;
        }


        protected override void ShowContextMenu(TreeViewWin treeView, Point clickLocation)
        {
            ShowContextMenuWasCalled = true;
            // do nothin if we show the menu the form won't close
            return;
        }

        protected override ITreeNode GetTreeNode(TreeViewWin treeView, Point clickLocation)
        {
            GetTreeNodeWasCalled = true;
            return _treeNode;
        }
    }

}
